﻿using System;
using System.Runtime.CompilerServices;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components.Surface
{
    [Serializable]
    public class AnisotropicPhong : SurfaceBsdf
    {
        public RgbSpectrum Kd, Ks;
        private float nu, nv;

        public AnisotropicPhong(RgbSpectrum kd, RgbSpectrum ks, float u, float v)
        {
            this.nu = u;
            this.nv = v;
            this.Kd = kd;
            this.Ks = ks;

        }

        public override void f(ref Vector lwo, ref Vector lwi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            CreateFrame(ref N);
            var wi = WorldToLocal(ref lwi);
            var wo = WorldToLocal(ref lwo);
            EvalBrdf(ref wo, ref wi,ref N, out fs);
        }

        private void EvalBrdf(ref  Vector wo, ref  Vector wi, ref Normal N, out RgbSpectrum fs)
        {
            Vector wh = (wi + wo).Normalize();
            float ior = 1.2f;
            float k = ((1f - ior)/(1f + ior));
            k *= k;

            var nh = Vector.Dot(ref wh, ref N);
            Vector u, v;
            Normal n = N;
            Vector.CoordinateSystem(ref n, out u, out v);
            var hu = Vector.Dot(ref wh, ref u);
            var hv = Vector.Dot(ref wh, ref v);
            var nwi = Vector.Dot(ref n, ref wi);
            var nwo = Vector.Dot(ref n, ref wo);
            var spec = ((MathLab.Sqrt((nu + 1f)*(nv + 1f)))/(8f*MathLab.M_PI))*
                       (((float) MathLab.Pow(nh, nu*(hu*hu) + nv*(hv*hv)))/(k*Math.Max(nwi, nwo)))
                       *FresnelController.Schlick_FresnelTerm(Ks, Vector.Dot(ref wh, ref wi));

            var diff = (28f*Kd/(23f*MathLab.M_PI))*(1f - Ks)*(Schlick(nwi)*Schlick(nwo));
            fs = spec + diff;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float Schlick(float nk)
        {
            return (1f - ((1f - (nk / 2)) * (1f - (nk / 2)) * (1f - (nk / 2)) * (1f - (nk / 2)) * (1f - (nk / 2))));
        }

        protected override void EvalParams(ref SurfaceTextureData surfaceData)
        {
            Kd = surfaceData.Diffuse;// RgbSpectrum.Max(ref surfaceData.Diffuse, ref Kd) * 0.5f;
            Ks = surfaceData.Specular;// RgbSpectrum.Max(ref surfaceData.Specular, ref Ks) * 0.5f;
        }

        public override void Sample_f(ref Vector wo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)

        {
            result.Lambda = 0f;

            this.CreateFrame(ref shadeN);
            this.EvalParams(ref surfaceData);
            float phi = (float)Math.Atan(MathLab.Sqrt((1f + nu) / (1f + nv)) * Math.Tan(u0 * MathLab.M_PI));
            float p = (float)(nu * Math.Cos(phi) * Math.Cos(phi) + nu * Math.Sin(phi) * Math.Sin(phi));

            float cosp = (float)MathLab.Pow(1.0 - u1, 1.0 / p);
            var wi = Vector.SphericalDirection(MathLab.Sin(phi), MathLab.Cos(phi), (float)Math.Acos(cosp));
            var wh = (wo + wi).Normalize();
            result.Type = BrdfType.Glossy;
            result.Pdf = (MathLab.Sqrt((nu + 1f) * (nv + 1f)) / 2f * MathLab.M_PI) * Vector.Dot(ref wh, ref N) * p;
            result.Pdf /= 4f * Vector.Dot(ref wo, ref wh);
            this.EvalBrdf(ref wo, ref wi, ref N, out result.F);
            result.Wi = LocalToWorld(ref wi);
        }

        public override BrdfType Type
        {
            get { return BrdfType.Glossy; }
        }

        public override bool IsDiffuse()
        {
            return true;
        }

        public override bool IsSpecular()
        {
            return true;
        }
    }
}